package com.lj.queue.service.impl;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.lj.common.exception.CommonException;
import com.lj.mp.standard.StandardServiceImpl;
import com.lj.mp.utils.PageQueryUtils;
import com.lj.queue.annotation.DelayQueueHandler;
import com.lj.queue.entity.DelayQueueMeta;
import com.lj.queue.handler.DelayMessageHandler;
import com.lj.queue.mapper.DelayQueueMetaMapper;
import com.lj.queue.param.DelayQueueMetaPageParam;
import com.lj.queue.param.DelayQueueMetaSaveParam;
import com.lj.queue.param.DelayQueueMetaUpdateParam;
import com.lj.queue.properties.DelayQueueProperties;
import com.lj.queue.result.DelayQueueMetaInfoResult;
import com.lj.queue.result.DelayQueueMetaPageResult;
import com.lj.queue.service.DelayQueueMetaService;
import com.lj.queue.utils.DelayQueueUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.StringJoiner;


/**
 * <p>
 * 延迟队列数据表 服务实现类
 * </p>
 *
 * @author lj
 * @since 2025-07-17 10:38:03
 */
@Service
@Slf4j
public class DelayQueueMetaServiceImpl extends StandardServiceImpl<DelayQueueMetaMapper, DelayQueueMeta> implements DelayQueueMetaService {

    @Resource
    private List<DelayMessageHandler> delayHandlerList;
    @Resource
    private DelayQueueProperties delayQueueProperties;

    @Override
    public IPage<DelayQueueMetaPageResult> page(DelayQueueMetaPageParam param) {
        return this.page(PageQueryUtils.getPage(param), getQueryWrapper(param)).convert(DelayQueueMetaPageResult::of);
    }

    private LambdaQueryWrapper<DelayQueueMeta> getQueryWrapper(DelayQueueMetaPageParam param) {
        return lambdaQueryWrapper()
                .eq(ObjectUtil.isNotEmpty(param.getName()), DelayQueueMeta::getName, param.getName())
                .orderByDesc(CollUtil.isEmpty(param.getSortList()), DelayQueueMeta::getId);
    }

    @Override
    public DelayQueueMetaInfoResult info(Long id) {
        return DelayQueueMetaInfoResult.of(this.getById(id));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void save(DelayQueueMetaSaveParam param) {
        DelayQueueMeta entity = param.toEntity();
        // 最后预加载时间不能为空否则，首次预加载会有问题
        entity.setLastLoadTime(LocalDateTime.now());
        // 判断时间
        verifyTime(entity);
        this.save(entity);
        // 创建延迟队列
        if (delayQueueProperties.isEnableExecute()) {
            DelayQueueUtils.createDelayQueue(entity, getDelayHandlerList(entity));
        }
    }

    @Override
    public void update(DelayQueueMetaUpdateParam param) {
        DelayQueueMeta queueMeta = this.getById(param.getId());
        if (queueMeta == null) {
            throw new CommonException("队列信息不存在!");
        }
        DelayQueueMeta entity = param.toEntity();
        verifyTime(entity);
        // 使用乐观锁来记录队列信息被修改的版本
        entity.setVersion(queueMeta.getVersion());
        // 执行间隔会在下次触发预加载时生效(todo 这里如果立刻触发预加载的话，会有两个预加载消息互相干扰，待解决!)
        this.updateById(entity);
    }

    private void verifyTime(DelayQueueMeta entity) {
        if (entity.getPreloadDistance() == null) {
            entity.setPreloadDistance(delayQueueProperties.getDefaultPreloadDistance());
        }
        if (entity.getPreloadDuration() == null) {
            entity.setPreloadDuration(delayQueueProperties.getDefaultPreloadDuration());
        }
        if (entity.getPreloadDistance() > entity.getPreloadDuration()) {
            throw new CommonException("预加载时长需要大于等于预加载时间间隔!");
        }
        if (entity.getTimeUnit() == null) {
            entity.setTimeUnit(delayQueueProperties.getDefaultTimeUnit());
        }
    }

    @Override
    public void delete(List<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return;
        }
        if (delayQueueProperties.isEnableExecute()) {
            // 删除队列 (todo 如果是分布式部署执行的话，这里是无法删除真实的队列的，待解决!)
            List<DelayQueueMeta> delayQueueMetaList = this.listByIds(ids);
            for (DelayQueueMeta delayQueueMeta : delayQueueMetaList) {
                DelayQueueUtils.removeQueue(delayQueueMeta.getName());
            }
        }
        this.removeByIds(ids);
    }

    @EventListener
    public void init(ApplicationReadyEvent event) {
        if (!delayQueueProperties.isEnableExecute()) {
            // 如果不开启执行就无需进行初始化队列等操作了
            return;
        }
        // 加载所有队列
        List<DelayQueueMeta> queueMetaList = this.list();
        if (CollUtil.isEmpty(queueMetaList)) {
            return;
        }
        for (DelayQueueMeta delayQueueMeta : queueMetaList) {
            DelayQueueUtils.createDelayQueue(delayQueueMeta, getDelayHandlerList(delayQueueMeta));
        }
    }

    private List<DelayMessageHandler<Object>> getDelayHandlerList(DelayQueueMeta delayQueueMeta) {
        List<DelayMessageHandler<Object>> result = new ArrayList<>();
        StringJoiner sj = new StringJoiner("->");
        for (DelayMessageHandler<Object> delayMessageHandler : delayHandlerList) {
            DelayQueueHandler annotation = AnnotationUtil.getAnnotation(delayMessageHandler.getClass(), DelayQueueHandler.class);
            if (annotation == null) {
                continue;
            }
            if (ArrayUtil.contains(annotation.value(), "*") ||
                    ArrayUtil.contains(annotation.value(), delayQueueMeta.getName())) {
                result.add(delayMessageHandler);
                sj.add(ClassUtil.getClassName(delayMessageHandler, true));
            }
        }
        log.info("队列:[{}] 延迟处理器->{}", delayQueueMeta.getName(), sj);
        return result;
    }
}
